#include"thread.h"
#include<fcntl.h>
#include<signal.h>
#include<sys/wait.h>
#define ARGC 10
#define READ_BUFF 4096
void recv_file(int sockfd,char* name)//上传
{
    if(name==NULL)
    {
        return;
    }
    char buf[128]={0};
    if(recv(sockfd,buf,127,0)<=0)
    {
        printf("recv err\n");
        return;
    }
    if(strncmp(buf,"ok#",3)!=0)
    {
        printf("456454545456");
        return;
    }
    char *s=buf;
    s+=3;//MD5值开始
    
   int m_fd=open("md5.c",O_RDONLY|O_CREAT,0600);
    char md_res_buff[1024]={0};
    read(m_fd,md_res_buff,1024);

    char*ss=strtok(md_res_buff,"\n");
    while(ss!=NULL)
    {
        if(strcmp(s,ss)==0)
        {
            send(sockfd,"#samefile#",10,0);
           // printf("秒传成功\n");
            close(sockfd);
            break;
        }
        ss=strtok(NULL,"\n");
    }
    send(sockfd,"#can send#",10,0);
    char buff[128]={0};
    if(recv(sockfd,buff,127,0)<=0)
    {
        return;
    }
    if(strncmp(buff,"ok#",3)!=0)
    {
        printf("ERROR!!!\n");
        return;
    }
   int size=0;
   printf("文件大小:%s\n",buff+3);//输出文件大小
   sscanf(buff+3,"%d",&size);

    int fd=open(name,O_WRONLY|O_CREAT,0600);//创建文件
    if(fd==-1)
    {
        printf("13546545465\n");
        send(sockfd,"err",3,0);
        return;
    }
    printf("2446546464654560");
    send(sockfd,"ok",2,0);
    char recvbuff[1024]={0};
    int num=0;
    //int curr_size=0;
    while ((num=recv(sockfd,recvbuff,1024,0))>0)
    {
       write(fd,recvbuff,num);
    //    curr_size+=num;
    //    float f=curr_size*100/size;
    //    printf("文件上传:%.2f%%\r",f);
    //    fflush(stdout);
    //    if(curr_size>=size)
    //    {
    //        break;
    //    }
    }
    close(sockfd);
    //printf("\n完成文件上传!\n");
    return;
    
}
void send_file(int c,char*name)
{
    if(name==NULL)
    {
        send(c,"err#no name",11,0);
        return;
    }
    int fd=open(name,O_RDONLY|__O_LARGEFILE);
    if(fd==-1)
    {
        printf("no this file!!!\n");
        send(c,"err",3,0);
        return;
    }
    int size=lseek(fd,0,SEEK_END);
    lseek(fd,0,SEEK_SET);
    char res_buff[128]={0};
    sprintf(res_buff,"ok#%d",size);
    send(c,res_buff,strlen(res_buff),0);//发送文件大小给客户端比较
    char cli_status[64]={0};
    if(recv(c,cli_status,63,0)<=0)//接收客户端存在的文件大小
    {
        close(fd);
        return;
    }
    if(strncmp(cli_status,"err",3)==0)
    {
        printf("文件为空，无需发送！！");
    }
    if(strncmp(cli_status,"ok",2)!=0)//如果是ok的话，说明文件不存在，可以发送
    {
        close(fd);
        return;
    }
    if(strncmp(cli_status+3,"size#",5)==0)//文件大小不为零，说明是断点续传
    {
        int d_size=0;
        sscanf(cli_status+8,"%d",&d_size);//ok#size#后读取客户端文件大小

        if(d_size<=0)
        {
            send(c,"err_cli_size",12,0);
            return;
        }
        lseek(fd,d_size,SEEK_SET);
        int d_num=0;
        char d_sendbuff[1024]={0};
        while((d_num=read(fd,d_sendbuff,1024))>0)
        {
            send(c,d_sendbuff,d_num,0);//从客户端大小的地方发送文件
        }
        close(fd);
        return;
    }
    int num=0;
    char sendbuff[1024]={0};
    while((num=read(fd,sendbuff,1024))>0)
    {
        send(c,sendbuff,num,0);
    }
    close(fd);
    printf("文件下载完成！！！\n");
    return;
}
void* work_thread(void*arg)
{
    int c=(int)arg;
    while(1)
    {
        char buff[256]={0};
        int n=recv(c,buff,255,0);//ls,mc,get a.c
        printf("buff=%s\n",buff);
        if(n<=0)
        {
            printf("one client over\n");
            break;
        }
        int i=0;
        char* myargv[ARGC]={0};
        char*ptr=NULL;
        char*s=strtok_r(buff," ",&ptr);
        //strtok(char str[],const char *delim);其中str是要分解的字符串，delim是字符串中用来分解的字符，该函数返回分解后的字符串的起始位置指针
        //注意：在对一个长字符串进行分割的时候，第一次调用时，strtok函数的第一个参数传入要分割的字符串，而第二次以及后边再次调用该函数的时候，strtok函数的第一个参数应该传入NULL，
        //这是因为在strtok第一个参数为NULL的时候，该函数默认使用上一次未分割完的字符串的未分割的起始位置作为本次分割的起始位置，直到分割完成为止。
        //函数分割原理:strtok函数在分割时其实是将字符串内的分隔符替换成了'\0'，即字符串结束标志字符，这样在打印时都会使得前边的字符串看起来是一个完整的字符串。
        //虽然strtok函数的第一个参数可以是一个指针，但是不能是字符串常量指针，因为源字符串要发生变化，所以如果输入的字符是一个字符串常量指针的时候，是不能够直接传递给第一个参数的，可以拷贝到一个字符串数组中，再进行分解，也可以考虑使用strstr函数，strchr函数
        //delim中的字符均可以作为分隔符，而非严格匹配。可以把delim想象成分隔符的集合
        //如果strtok函数要分解的字符串中的首字符就是分隔符，那么strtok函数就会忽略第一个字符，直接从下一个分隔符算起
        //因为strtok函数不是线程安全的，因为strtok中剩余字符串是存储在一个静态变量中，因此，多线程在使用该静态变量是引起冲突，而有了strtok_r函数则使用用户传入的指针为每个用户saveptr重新申请变量它是线程安全的------
        //char *strtok_r(char *str, const char *delim, char **saveptr);saveptr是一个供内部使用的指针，用于保存上次分割剩下的字符串
        //
        while(s!=NULL)
        {
            myargv[i++]=s;
            s=strtok_r(NULL," ",&ptr);
        }
        char *cmd=myargv[0];//cmd
        if(cmd==NULL)
        {
            send(c,"err",3,0);
            continue;
        }
        if(strcmp(cmd,"get")==0)
        {
            send_file(c,myargv[1]);//下载
        }
        else if(strcmp(cmd,"rm")==0)
        {
            if(remove(myargv[1]==0))//remove函数会删除其参数指定的文件 参数如果是一个文件，则调用unlink()处理；若参数为一个目录，则调用rmdir处理
            //执行unlink()函数并不一定会真正的删除文件，它先会检查文件系统中此文件的连接数是否为1，如果不是1说明此文件还有其他链接对象，因此只对此文件的连接数进行减1操作。
            //若连接数为1，并且在此时没有任何进程打开该文件，此内容才会真正地被删除掉。在有进程打开此文件的情况下，则暂时不会删除，直到所有打开该文件的进程都结束时文件就会被删除。rmdir删除空目录，非空的目录不能删除
            {
                printf("%s has removed\n",myargv[1]);
            }
            else
            {
                perror("remove err");
            }
        }
        else if(strcmp(cmd,"put")==0)
        {
            printf("file=%s\n",myargv[1]);
            recv_file(c,myargv[1]);
            char buff[128]={0};
            sprintf(buff,"./my.sh %s",myargv[1]);
            system(buff);
        }
        else
        {
            //其本质是一个伪文件(实为内核缓冲区)
            //由两个文件描述符引用，一个标识读端，一个表示写端
            //规定数据从管道的写端流入管道，从读端流出
            //管道的原理：管理实为内核使用环形队列机制，借助内核缓冲区(4k)实现
            //管道局限性:数据自己读不能自己写；数据一旦被读走，便不在管道中存在，不可反复读取；由于管道采用半双工通信方式。因此，数据只能在一个方向上流动；；只能在公有祖先的进程间使用管道；
            int pipefd[2];
            pipe(pipefd);//创建管道；成功：0；失败:-1，设置errno
            //函数调用成功返回r/w两个文件描述符。无需open,但需手动close.规定：fd[0]->r;fd[1]->w,就像0对应标准输入，1对应标准输出一样。向管道文件读写数据其实是在读写内核缓冲区
            //管道创建成功之后，创建该管道的父进程同时掌握着管道的读端和写端。如何实现父子进程间通信呢？
            pid_t pid=fork();//pid_t定义的类型都是进程号类型。这个语句的意思是定义了一个pid_t类型的变量pid，fork()函数返回一个进程号，这个进程号赋给了pid
            if(pid==-1)
            {
                send(c,"err",3,0);
                continue;
            }
            if(pid==0)
            {
                dup2(pipefd[1],1);//把stdout重定向到管道
                dup2(pipefd[1],2);//把stderr重定向到管
               // close(pipefd[0]);//关闭读端
                execvp(cmd,myargv);//表示第一个参数path不用输入完整路径，只有给出命令名即可，它会在环境变量PATH当中查找命令
                //exec函数族提供了一个在进程中启动另一个程序执行的方法。它可以根据指定的文件名或目录名找到可执行文件，并用它来取代原调用进程的数据段、代码段和堆栈段，
                //在执行完之后，原调用进程的内容除了进程号外，其他全部被新的进程替换了。另外，这里的可执行文件既可以是二进制文件，也可以是Linux下任何可执行的脚本文件。
                //我们用fork函数创建新进程后，经常会在新进程中调用exec函数去执行另外一个程序。当进程调用exec函数时，该进程被完全替换为新程序。因为调用exec函数并不创建新进程，所以前后进程的ID并没有改变。
                //那么带v的函数是什么意思呢？ execv、execvp、execve，应先构造一个指向各参数的指针数组，然后将该数组的地址作为这些函数的参数。也就是在函数外面指定好指针数组，exec函数直接使用
                //那么我们使用fork函数和exec函数搭配使用是什么效果呢？之前我们说fork函数创建子进程也可以做不同的事情，那么这里就需要子进程返回后马上调用exec函数，不改变id去执行其他的可执行文件
                perror("cmd not exist");
                exit(0);
            }
            //重点：fork之后是父进程先执行还是子进程先执行，并没有固定顺序，这取决于操作系统的调度算法，在这里，因为fork之后，父进程会继续修改数据页和栈页，即继续执行下边的代码，而内核也会为子进程复制那些要被父进程修改的页
            //但是，如果子进程中有替换函数时，那么这个复制的页就白白浪费掉了，而内核会根据这些特点决定此时先执行子进程，如此一来，等到下一次调度父进程是，就无需复制内存页了
            close(pipefd[1]);//关闭写端
            wait(NULL);
            char readbuff[READ_BUFF]={"OK#"};
            read(pipefd[0],readbuff+3,READ_BUFF-4);
            send(c,readbuff,strlen(readbuff),0);
            close(pipefd[0]);//关闭读端
        }
    }
    close(c);
}
//为什么要用到管道？如果客户端发过来的是系统命令的话，比方说是ls,那么现在面临的问题就是如何将这个命令对应的答案发送给客户端，在解决这个问题之前，首先要获取这个答案
//那么如何获取这个答案呢？我们知道，ls是查看当前目录下有哪些文件的，且默认显示在屏幕上，我们现在不想让它显示在屏幕上，要设法将它存储在buff中，发送给客户端，那么先要写数据，再要读数据，我们首先想到的就是管道，
//创建一个匿名管道，他有两个端，一个是读端，一个是写端，恰好能解决问题，但是匿名管道是适用于父子进程之间通信的，因为管道没有实体，也就是没有管道文件，所以只能通过fork来复制父进程fd1的描述符来达到通信的目的

//拓展:对于命名管道，他可以在不相关的进程间也能相互通信。因为命名管道，提前创建了一个类型为管道的设备文件，在进程里只要使用这个设备文件，就可以相互通信。
//与管道的区别：提供了一个路径名与之关联，以FIFO文件的形式存储于文件系统中，能够实现任何两个进程之间通信。而匿名管道对于文件系统是不可见的，它仅限于在父子进程之间的通信。
//FIFO是一个设备文件，在文件系统中以文件名的形式存在，因此即使进程与创建FIFO的进程不存在血缘关系也依然可以通信，前提是可以访问该路径。
//FIFO(first input first output)总是遵循先进先出的原则，即第一个进来的数据会第一个被读走。
//#include <sys/stat.h>
//int  mknod(const  char*  path, mode_t mod,  dev_t dev);
//int  mkfifo(const  char* path,  mode_t  mod);
//这两个函数都能创建一个FIFO文件，该文件是真实存在于文件系统中的。函数 mknod 中参数 path 为创建命名管道的全路径； mod 为创建命名管道的模式，指的是其存取权限； dev为设备值，改值取决于文件创建的种类，它只在创建设备文件是才会用到。
//返回值：这两个函数都是成功返回 0 ，失败返回 -1




int thread_start(int c)
{
    pthread_t id;
    int res=pthread_create(&id,NULL,work_thread,(void*)c);
    //第一个参数为线程标识符的指针
    //第二个参数是创建线程时设置的线程属性
    //第三个参数是线程运行时的起始地址。回调函数
    //最后一个参数是运行函数的参数。线程函数的参数，线程传参数
    //返回值：0表示创建线程成功，非0表示失败，常见的错误码为EAGAIN和EINVAL，前者表示系统限制创建新的线程；后者表示第二个参数代表的线程属性值非法
    //Unix线程传参数时，注意事项：线程函数要求的格式为void *thread_function(void *argc)
    //编译时要连接静态库:pthread_create()系列函数时Unix库函数，不是Linux函数，编译时需要连接静态库，所以编译时要添加选项-lpthread
    if(res!=0)
    {
        return -1;
    }
    return 0;
}